﻿using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;

namespace Communication.Help;

/// <summary>
///     提供管道操作的辅助类
/// </summary>
public static class PipeHelp
{
    /// <summary>
    ///     通过不安全访问器获取管道中未消耗的字节长度
    /// </summary>
    /// <param name="pipe">目标管道实例</param>
    /// <returns>未消耗的字节长度</returns>
    [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_unconsumedBytes")]
    public static extern ref long GetLength(this Pipe pipe);

    /// <summary>
    ///     在只读序列中查找指定值的首次出现位置
    /// </summary>
    /// <typeparam name="T">序列元素类型，必须实现IEquatable接口</typeparam>
    /// <param name="data">要搜索的只读序列</param>
    /// <param name="value">要查找的值</param>
    /// <returns>如果找到则返回位置，否则返回null</returns>
    public static SequencePosition? PositionOf<T>(this ReadOnlySequence<T> data, ReadOnlySpan<T> value)
        where T : IEquatable<T>?
    {
        // 如果序列不是单段的，则使用多段查找方法
        if (!data.IsSingleSegment) return PositionOfMultiSegment(data, value);

        // 在单个段中查找值
        var index = data.FirstSpan.IndexOf(value);
        return index == -1 ? null : data.GetPosition(index);
    }

    /// <summary>
    ///     在多段只读序列中查找指定值的首次出现位置
    /// </summary>
    /// <typeparam name="T">序列元素类型，必须实现IEquatable接口</typeparam>
    /// <param name="source">要搜索的多段只读序列</param>
    /// <param name="value">要查找的值</param>
    /// <returns>如果找到则返回位置，否则返回null</returns>
    private static SequencePosition? PositionOfMultiSegment<T>(in ReadOnlySequence<T> source, ReadOnlySpan<T> value)
        where T : IEquatable<T>?
    {
        var position = source.Start;
        var result = position;

        // 遍历序列的所有段
        while (source.TryGet(ref position, out var memory))
        {
            // 在当前段中查找值
            var index = memory.Span.IndexOf(value);
            if (index != -1)
            {
                return source.GetPosition(index, result);
            }

            // 如果到达序列末尾，则退出循环
            if (position.GetObject() == null)
            {
                break;
            }

            result = position;
        }

        return null;
    }
}